package com.leyantech.ecp.agenx.common.utils;

import com.leyantech.utility.statsd.StatsDMetricsReporter;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.caffeine.MetricsStatsCounter;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * @author eahau, {@literal <eahau@foxmail.com>}
 * @date 2022-12-07.
 */
public final class CacheUtils {

  static final MetricRegistry metricRegistry = new MetricRegistry();

  static {
    StatsDMetricsReporter.forRegistry(metricRegistry)
        .prefixedWith("CaffeineCache")
        .build()
        .start(10, TimeUnit.SECONDS);
  }

  private CacheUtils() {
  }

  static final Config CACHE_CONFIG = ConfigService.getConfig("cache");

  public static <K, V> Supplier<LoadingCache<K, V>> buildCache(
      String name,
      CacheLoader<K, V> cacheLoader) {

    Objects.requireNonNull(name, "name is null.");
    Objects.requireNonNull(cacheLoader, "cacheLoader is null.");

    final MetricsStatsCounter metricsStatsCounter = new MetricsStatsCounter(metricRegistry, name);

    final Function<String, LoadingCache<K, V>> cacheSupplier = spec -> Caffeine
        .from(spec)
        .recordStats(() -> metricsStatsCounter)
        .build(cacheLoader);

    return new Supplier<LoadingCache<K, V>>() {

      volatile LoadingCache<K, V> cache = cacheSupplier.apply(CACHE_CONFIG.getProperty(name, ""));

      {
        CACHE_CONFIG.addChangeListener(changeEvent -> {
          final ConfigChange change = changeEvent.getChange(name);
          if (change != null) {
            final LoadingCache<K, V> newCache = cacheSupplier.apply(change.getNewValue());

            newCache.putAll(cache.asMap());

            this.cache = newCache;
          }
        });
      }

      @Override
      public LoadingCache<K, V> get() {
        return cache;
      }

    };
  }

}
